/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.bef.core.action.modify;


import com.inspur.edp.bef.api.be.IBEContext;
import com.inspur.edp.bef.core.action.base.ActionUtil;
import com.inspur.edp.bef.core.be.BusinessEntity;
import com.inspur.edp.bef.spi.action.AbstractAction;
import com.inspur.edp.cef.entity.accessor.base.AccessorComparer;
import com.inspur.edp.cef.entity.changeset.InnerUtil;
import com.inspur.edp.cef.entity.changeset.ModifyChangeDetail;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.entity.entity.IEntityDataCollection;
import com.inspur.edp.cef.entity.entity.IValueObjData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

public class FullModifyAction extends AbstractAction<IEntityData> {

  private final IEntityData data;
  private final BusinessEntity entity;

  public FullModifyAction(IBEContext beContext, IEntityData data) {
    super(beContext);
    this.data = data;
    entity = ActionUtil.getRootEntity(this);
  }

  @Override
  public void execute() {
    IEntityData org = entity.getBEContext().getCurrentData();
    if (org == null) {
      throw new RuntimeException("编辑的数据不存在" + data.getID());
    }
    ModifyChangeDetail change = new ModifyChangeDetail(data.getID());
    modify(org, data, change, change, Collections.emptyList(), Arrays.asList(data.getID()));
  }

  private void modify(IEntityData org, IEntityData data, ModifyChangeDetail rootChg,
      ModifyChangeDetail currChg, List<String> nodePath, List<String> idPath) {
    buildPropChanges(org, data, currChg);
    if (!currChg.getPropertyChanges().isEmpty()) {
      entity.modify(rootChg);
    }

    currChg.getPropertyChanges().clear();
    Map<String, IEntityDataCollection> childs = data.getChilds();
    if (childs == null || childs.isEmpty()) {
      return;
    }
    for (Entry<String, IEntityDataCollection> childEntry : childs.entrySet()) {
      List<String> childNodePath = new ArrayList<>(nodePath);
      childNodePath.add(childEntry.getKey());
//      List<String> childIdPath = new ArrayList<>(idPath);
      List<IEntityData> addings = new ArrayList();
      IEntityDataCollection orgChilds = org.getChilds().get(childEntry.getKey());
      for (IEntityData child : childEntry.getValue()) {
        IEntityData orgChild = orgChilds.tryGet(child.getID());
        if (orgChild != null) { // 修改
          ModifyChangeDetail childChange = new ModifyChangeDetail(child.getID());
          currChg.addChildChangeSet(childEntry.getKey(), childChange);
          List<String> childIdPath = new ArrayList<>(idPath);
          childIdPath.add(child.getID());
          modify(orgChild, child, rootChg, childChange, childNodePath, childIdPath);
          currChg.removeChildChangeSet(childEntry.getKey(), childChange.getID());
        } else { //新增
          addings.add(child);
        }
      }

      if (orgChilds.size() + addings.size() != childEntry.getValue().size()) {
        List<String> deletings = new ArrayList<>();
        for (IEntityData orgChild : orgChilds) {
          IEntityData child = childEntry.getValue().tryGet(orgChild.getID());
          if (child == null) { // 修改
            deletings.add(orgChild.getID());
          }
        }
        entity.deleteChild(childNodePath, idPath, deletings);
      }

      if (!addings.isEmpty()) {
        addChild(childNodePath, idPath, addings);
      }
    }
  }

  private void addChild(List<String> nodePath, List<String> idPath,
      Collection<IEntityData> addings) {
    for (IEntityData data : addings) {
      Map<String, Object> values = buildDefaultValues(data);
      IEntityData newData = entity.retrieveDefaultChild(nodePath, idPath, values);
      Map<String, IEntityDataCollection> childs = data.getChilds();
      if (childs == null || childs.isEmpty()) {
        continue;
      }
      List<String> childIdPath = new ArrayList<>(idPath);
      childIdPath.add(newData.getID());
      for (Entry<String, IEntityDataCollection> childEntry : childs.entrySet()) {
        if (childEntry.getValue() == null || childEntry.getValue().isEmpty()) {
          continue;
        }
        List<String> childNodePath = new ArrayList<>(nodePath);
        childNodePath.add(childEntry.getKey());
        addChild(childNodePath, childIdPath, childEntry.getValue());
      }
    }
  }

  private static void buildPropChanges(IEntityData org, IEntityData data,
      ModifyChangeDetail change) {
    for (String prop : data.getPropertyNames()) {
      if ("ID".equalsIgnoreCase(prop)) {
        continue;
      }
      Object newValue = data.getValue(prop);
      if (newValue instanceof IEntityDataCollection) {
        continue;
      }
      if (org == null || !equals(newValue, org.getValue(prop))) {
        change.putItem(prop, (newValue != null && newValue instanceof IValueObjData) ? InnerUtil
            .buildNestedChange(newValue) : newValue);
      }
    }
  }

  public static boolean equals(Object value1, Object value2) {
    if (value1 instanceof byte[] && value2 instanceof byte[]) {
      return AccessorComparer.equals((byte[]) value1, (byte[]) value2);
    } else {
      return AccessorComparer.equals(value1, value2);
    }
  }

  private static Map<String, Object> buildDefaultValues(IEntityData data) {
    Set<String> childs = data.getChilds().keySet().stream().map(item -> item.concat("s")).collect(
        Collectors.toSet());
    Map<String, Object> values = new HashMap<>();
    for (String prop : data.getPropertyNames()) {
      if ("ID".equalsIgnoreCase(prop)) {
        continue;
      }
      if (childs != null && childs.contains(prop)) {
        continue;
      }
      Object newValue = data.getValue(prop);
      if (newValue instanceof IEntityDataCollection) {
        continue;
      }
      values.put(prop, newValue);
    }
    return values;
  }
}
